package com.express.gateway.verticle;

import com.express.gateway.common.CommonCons;
import com.express.gateway.common.DataStoreConstant;
import com.express.gateway.common.EventBusTopicConst;
import com.express.gateway.common.GatewayAttribute;
import com.express.gateway.common.util.StrUtil;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.jdbc.JDBCClient;
import io.vertx.ext.sql.ResultSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.text.MessageFormat;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * 网关应用与API持久化的Verticle
 *
 * @author flower
 */
public class DataBaseAccessVerticle extends AbstractVerticle {
    private static final Logger LOG = LogManager.getLogger(DataBaseAccessVerticle.class);

    /**
     * JDBC客户端
     */
    private JDBCClient jdbcClient = null;

    @Override
    public void start(Promise<Void> fut) throws Exception {
        LOG.info("start Data Verticle.");
        initShorthand();// 初始化简写后的常量数据
        JsonObject dbConfig = config();
        String url = dbConfig.getString("url");
        if (dbConfig.getString("url").contains("jdbc:sqlite:")) {
            String temp = url.replace("jdbc:sqlite:", "");
            if (!temp.contains("/")) {
//				dbConfig.put("url", "jdbc:sqlite:" + PathUtil.getPathString(temp));
                dbConfig.put("url", "jdbc:sqlite:" + temp);
            }
        }
        jdbcClient = JDBCClient.createShared(vertx, dbConfig, GatewayAttribute.NAME);

        // application operation address
        vertx.eventBus().consumer(EventBusTopicConst.FIND_APP, this::findApp);
        vertx.eventBus().consumer(EventBusTopicConst.GET_APP, this::getApp);
        vertx.eventBus().consumer(EventBusTopicConst.ADD_APP, this::addApp);
        vertx.eventBus().consumer(EventBusTopicConst.DEL_APP, this::delApp);
        vertx.eventBus().consumer(EventBusTopicConst.UPD_APP, this::updateApp);
        // api operation address
        vertx.eventBus().consumer(EventBusTopicConst.FIND_API_ALL, this::findApi);
        vertx.eventBus().consumer(EventBusTopicConst.FIND_API_BY_PAGE, this::findApiByPage);
        vertx.eventBus().consumer(EventBusTopicConst.GET_API, this::getApi);
        vertx.eventBus().consumer(EventBusTopicConst.ADD_API, this::addApi);
        vertx.eventBus().consumer(EventBusTopicConst.DEL_API, this::delApi);
        vertx.eventBus().consumer(EventBusTopicConst.UPD_API, this::updateApi);
        // blacklist
        vertx.eventBus().consumer(EventBusTopicConst.FIND_BLACKLIST, this::findBlacklist);
        vertx.eventBus().consumer(EventBusTopicConst.REPLACE_BLACKLIST, this::replaceBlacklist);
        LOG.info("start DATA Verticle successful");
        fut.complete();
    }

    // =============================================
    // =====================网关应用==================
    // =============================================
    /**
     * 网关应用的表名简写
     */
    private String APPTN = null;
    /**
     * 网关应用的主键列简写
     */
    private String APPIC = null;
    /**
     * 网关应用json字符串内容的列名简写
     */
    private String APPCC = null;
    /**
     * 网关应用通讯传输中获取json中存放主键的属性名简写
     */
    private String APPIN = null;
    /**
     * 网关应用通讯传输中获取json中存放json字符串对象的属性名简写
     */
    private String APPCN = null;

    /**
     * 获得所有应用程序
     *
     * @param msg
     */
    public void findApp(Message<String> msg) {
        String sql = MessageFormat.format("select {0} AS {1} ,{2} AS {3} from {4} ", APPIC, APPIN, APPCC, APPCN, APPTN);
        jdbcClient.query(sql, res -> {
            if (res.failed()) {
                msg.fail(500, res.cause().toString());
                LOG.error("执行查询 {}, 失败: {}", sql, res.cause().toString());
                return;
            }
            List<JsonObject> rows = res.result().getRows();
            if (Objects.isNull(rows)) {
                msg.reply(new JsonArray());
                return;
            }
            JsonArray array = new JsonArray();
            rows.forEach(va -> {
                if (va.getValue(APPCN) instanceof String) {
                    array.add(new JsonObject(va.getString(APPCN)));
                } else if (va.getValue(APPCN) instanceof JsonObject) {
                    array.add(va.getJsonObject(APPCN));
                }
            });
            msg.reply(array);
        });

    }

    /**
     * 获得一个应用程序,接收应用程序的字符串名字
     *
     * @param msg
     */
    public void getApp(Message<String> msg) {
        String name = msg.body();
        if (Objects.isNull(name)) {
            msg.fail(411, "the application name is null");
            return;
        }
        String sql = MessageFormat.format("select {0} AS {1} ,{2} AS {3} from {4} where {0} = ? ", APPIC, APPIN, APPCC, APPCN, APPTN);
        JsonArray params = new JsonArray();
        params.add(name);
        jdbcClient.queryWithParams(sql, params, res -> {
            if (res.failed()) {
                msg.fail(500, res.cause().toString());
                LOG.error("执行查询 {}, 失败: {}", sql, res.cause().toString());
                return;
            }
            List<JsonObject> rows = res.result().getRows();
            JsonObject result = Optional.ofNullable(rows).filter(r -> !r.isEmpty()).map(r -> r.get(0)).orElse(new JsonObject());
            msg.reply(result);
        });
    }

    /**
     * 添加应用程序,接收一个网关应用的json对象,
     *
     * @param msg
     */
    public void addApp(Message<JsonObject> msg) {
        if (Objects.isNull(msg.body())) {
            msg.fail(411, "the message body is null");
            return;
        }
        JsonObject body = msg.body();
        String sql = MessageFormat.format("insert into {0} ({1},{2}) values(?,?)", APPTN, APPIC, APPCC);
        JsonArray params = new JsonArray();
        params.add(body.getString(CommonCons.KEY_APP_NAME));
        params.add(body.getJsonObject(CommonCons.KEY_APP).toString());
        process(msg, sql, params);

    }

    /**
     * 删除一个应用程序,接收应用程序的字符串名字
     *
     * @param msg
     */
    public void delApp(Message<JsonObject> msg) {
        if (Objects.isNull(msg.body())) {
            msg.fail(411, "the message body is null");
            return;
        }
        String isql = MessageFormat.format("delete from {0} where {1} = ?", APITN, API_APPIC);
        JsonArray apiAppParam = new JsonArray();
        apiAppParam.add(msg.body());
        jdbcClient.updateWithParams(isql, apiAppParam, rs -> {
            if (rs.failed()) {
                msg.fail(500, rs.cause().toString());
                LOG.error("执行查询 {}, 失败: {}", isql, rs.cause().toString());
                return;
            }
            String sql = MessageFormat.format("delete from {0} where {1} = ?", APPTN, APPIC);
            JsonArray appParam = new JsonArray();
            appParam.add(msg.body());
            process(msg, sql, appParam);
        });

    }

    /**
     * 修改应用程序,接收一个网关应用的json对象
     *
     * @param msg
     */
    public void updateApp(Message<JsonObject> msg) {
        if (Objects.isNull(msg.body())) {
            msg.fail(411, "the message body is null");
            return;
        }
        JsonObject body = msg.body();
        String sql = MessageFormat.format("update {0} set {1}= ? where {2} = ?", APPTN, APPCC, APIIC);
        JsonArray params = new JsonArray();
        params.add(body.getJsonObject(CommonCons.KEY_APP).toString());
        params.add(body.getString(CommonCons.KEY_APP_NAME));
        process(msg, sql, params);
    }

    // =============================================
    // =====================APIS====================
    // =============================================
    /**
     * 存放API的表名简写
     */
    private String APITN = null;
    /**
     * 存放API的主键列简写
     */
    private String APIIC = null;
    /**
     * 存放API表中网关应用主键的列简写
     */
    private String API_APPIC = null;
    /**
     * 存放API,json字符串内容的列名简写
     */
    private String APICC = null;
    /**
     * API通讯传输中获取json中存放主键的属性名简写
     */
    private String APIIN = null;
    /**
     * API通讯传输中获取json中存放应用网关主键的属性名简写
     */
    private String API_APPIN = null;
    /**
     * API通讯传输中获取json中存放json字符串对象的属性名简写
     */
    private String APICN = null;

    /**
     * 查看所有API
     *
     * @param msg
     */
    public void findApi(Message<JsonObject> msg) {
        if (msg.body() == null || msg.body().getString(CommonCons.KEY_APP_NAME) == null) {
            msg.fail(411, "the application name is null");
            return;
        }
        String sql = MessageFormat.format("select {0} AS {1},{2} AS {3},{4} AS {5} from {6} where {2} = ? ", APIIC, APIIN, API_APPIC, API_APPIN, APICC, APICN, APITN);
        // 添加从请求中获取添加值并添加到查询条件中
        JsonArray params = new JsonArray();
        params.add(msg.body().getString(CommonCons.KEY_APP_NAME));
        jdbcClient.queryWithParams(sql, params, res -> {
            if (res.failed()) {
                msg.fail(500, res.cause().toString());
                LOG.error("执行查询 {}, 失败: {}", sql, res.cause().toString());
                return;
            }

            List<JsonObject> rows = res.result().getRows();
            JsonArray array = new JsonArray();
            rows.forEach(va -> {
                if (va.getValue(APICN) instanceof String) {
                    array.add(new JsonObject(va.getString(APICN)));
                } else if (va.getValue(APICN) instanceof JsonObject) {
                    array.add(va.getJsonObject(APICN));
                }
            });
            msg.reply(array);
        });
    }

    /**
     * 获得一个应用的所有API,传入应用程序的name,
     *
     * @param msg
     */
    public void findApiByPage(Message<JsonObject> msg) {
        if (msg.body() == null) {
            msg.fail(411, "the application name is null");
            return;
        }
        JsonObject body = msg.body();
        String countSql = MessageFormat.format("select count({0}) from {1} where {2} = ? ", APIIC, APITN, API_APPIC);
        JsonArray countParams = new JsonArray();
        countParams.add(body.getString(CommonCons.KEY_APP_NAME));
        jdbcClient.queryWithParams(countSql, countParams, count -> {
            if (count.failed()) {
                msg.fail(500, count.cause().toString());
                LOG.error("执行查询 {}, 失败: {}", countSql, count.cause().toString());
                return;
            }
            long dataCount = Optional.ofNullable(count.result()).map(ResultSet::getResults).map(row -> row.get(0)).map(c -> c.getLong(0)).map(v -> Long.parseLong(v.toString())).orElse(0L);
            // TODO 如果存储的数据库不支持limit 与offset 需要自己修改一下分页语句
            String sql = MessageFormat.format("select {0} AS {1},{2} AS {3},{4} AS {5} from {6} where {2} = ? limit ? offset ?", APIIC, APIIN, API_APPIC, API_APPIN, APICC, APICN, APITN);
            JsonArray params = new JsonArray().add(body.getString(CommonCons.KEY_APP_NAME));
            params.add(body.getInteger("limit"));
            params.add(body.getInteger("offset"));
            jdbcClient.queryWithParams(sql, params, res -> {
                if (res.failed()) {
                    msg.fail(500, res.cause().toString());
                    LOG.error("执行查询 {}, 失败: {}", sql, res.cause().toString());
                    return;
                }
                List<JsonObject> rows = res.result().getRows();
                JsonArray array = new JsonArray();
                rows.forEach(elm -> {
                    JsonObject row = null;
                    Object ro = elm.getValue(APICN);
                    if (ro instanceof String) {
                        row = new JsonObject(ro.toString());
                    } else if (ro instanceof JsonObject) {
                        row = (JsonObject) ro;
                    }
                    Optional.ofNullable(row).ifPresent(array::add);
                });
                msg.reply(new JsonObject().put("dataCount", dataCount).put(CommonCons.KEY_DATA, array));
            });
        });
    }

    /**
     * 获得一个api
     *
     * @param msg
     */
    public void getApi(Message<String> msg) {
        String name = msg.body();
        if (name == null) {
            msg.fail(411, "the application name is null");
            return;
        }
        String sql = MessageFormat.format("select {0} AS {1},{2} AS {3},{4} AS {5} from {6} where {0} = ? ", APIIC, APIIN, API_APPIC, API_APPIN, APICC, APICN, APITN);
        JsonArray params = new JsonArray();
        params.add(name);
        jdbcClient.queryWithParams(sql, params, res -> {
            if (res.failed()) {
                msg.fail(500, res.cause().toString());
                LOG.error("执行查询 {}, 失败: {}", sql, res.cause().toString());
                return;
            }
            List<JsonObject> rows = res.result().getRows();
            JsonObject result = null;
            if (rows != null && !rows.isEmpty()) {
                result = rows.get(0);
            } else {
                result = new JsonObject();
            }
            msg.reply(result);
        });
    }

    /**
     * 添加API,接收一个json对象,对象包含该app应用appName,网关的json对象api
     *
     * @param msg
     */
    public void addApi(Message<JsonObject> msg) {
        if (msg.body() == null) {
            msg.fail(411, "the application options is null");
            return;
        }
        JsonObject body = msg.body();
        String appName = body.getString(CommonCons.KEY_APP_NAME);

        Promise<Void> addApiFuture = Promise.promise();
        // 判断有没有存在该应用后执行
        addApiFuture.future().onComplete(check -> {
            String sql = MessageFormat.format("insert into {0} ({1},{2},{3}) values(?,?,?)", APITN, APIIC, API_APPIC, APICC);
            JsonArray params = new JsonArray();
            params.add(body.getString(CommonCons.KEY_API_NAME));
            params.add(appName);
            params.add(body.getJsonObject(CommonCons.KEY_API).toString());
            jdbcClient.updateWithParams(sql, params, res -> {
                if (res.failed()) {
                    msg.fail(500, res.cause().toString());
                    LOG.error("执行查询 {}, 失败: {}", sql, res.cause().toString());
                    return;
                }
                int result = res.result().getUpdated();
                msg.reply(result);
            });
        });
        String sql = MessageFormat.format("select {0} AS {1} ,{2} AS {3} from {4} where {0} = ? ", APPIC, APPIN, APPCC, APPCN, APPTN);
        JsonArray params = new JsonArray();
        params.add(appName);
        jdbcClient.queryWithParams(sql, params, res -> {
            if (res.failed()) {
                msg.fail(500, res.cause().toString());
                LOG.error("执行查询 {}, 失败: {}", sql, res.cause().toString());
                return;
            }
            List<JsonObject> rows = res.result().getRows();
            if (rows != null && !rows.isEmpty()) {
                addApiFuture.complete();
            } else {
                msg.fail(500, "网关应用不存在!");
                LOG.warn("执行添加API->查询应用程序 失败:网关应用不存在!");
            }
        });
    }

    /**
     * 删除一个API,接收应用程序的字符串名字
     *
     * @param msg
     */
    public void delApi(Message<JsonObject> msg) {
        if (msg.body() == null) {
            msg.fail(411, "the msg body is null");
            return;
        }
        String appName = msg.body().getString(CommonCons.KEY_APP_NAME);
        String apiName = msg.body().getString(CommonCons.KEY_API_NAME);
        if (StrUtil.isNullOrEmpty(appName, apiName)) {
            msg.fail(411, "the appName or apiName is null");
            return;
        }
        String sql = MessageFormat.format("delete from {0} where {1} = ? and {2} = ? ", APITN, API_APPIC, APIIC);
        JsonArray params = new JsonArray();
        params.add(appName);
        params.add(apiName);
        process(msg, sql, params);
    }

    /**
     * 修改API,接收一个json对象,其中包含主键name,内容content
     *
     * @param msg
     */
    public void updateApi(Message<JsonObject> msg) {
        if (msg.body() == null) {
            msg.fail(411, "the application name is null");
            return;
        }
        JsonObject body = msg.body();
        String sql = MessageFormat.format("update {0} set {1}= ? where {2} = ?", APITN, APICC, APIIC);
        JsonArray params = new JsonArray();
        params.add(body.getJsonObject(CommonCons.KEY_API).toString());
        params.add(body.getString(CommonCons.KEY_API_NAME));
        process(msg, sql, params);
    }

    private void process(Message<JsonObject> msg, String sql, JsonArray params) {
        jdbcClient.updateWithParams(sql, params, res -> {
            if (res.succeeded()) {
                int result = res.result().getUpdated();
                msg.reply(result);
            } else {
                msg.fail(500, res.cause().toString());
                LOG.error("执行查询 {}, 失败: {}", sql, res.cause().toString());
            }
        });
    }

    // =============================================
    // =====================黑名单列表=================
    // =============================================
    /**
     * 黑名单列表的表名简写
     */
    private String BLTN = null;
    /**
     * 黑名单列表的主键列简写
     */
    private String BLIC = null;
    /**
     * 黑名单列表jsonArray字符串内容的列名简写
     */
    private String BLCC = null;
    /**
     * 黑名单列表通讯传输中获取json中存放主键的属性名简写
     */
    private String BLIN = null;
    /**
     * 黑名单列表通讯传输中获取json中存放json字符串对象的属性名简写
     */
    private String BLCN = null;

    /**
     * 查看黑名单
     *
     * @param msg
     */
    public void findBlacklist(Message<JsonObject> msg) {
        String sql = MessageFormat.format("select {0} AS {1} ,{2} AS {3} from {4} ", BLIC, BLIN, BLCC, BLCN, BLTN);
        jdbcClient.query(sql, res -> {
            if (res.failed()) {
                msg.fail(500, res.cause().toString());
                LOG.error("执行查询 {}, 失败: {}", sql, res.cause().toString());
                return;
            }

            List<JsonObject> rows = res.result().getRows();
            JsonObject result = null;
            if (rows != null && !rows.isEmpty()) {
                result = rows.get(0);
            } else {
                result = new JsonObject();
            }
            if (result.getValue(BLCN) instanceof String) {
                result.put(BLCN, new JsonArray(result.getString(CommonCons.KEY_CONTENT)));
            }
            msg.reply(result);
        });
    }

    /**
     * 更新黑名单
     *
     * @param msg
     */
    public void replaceBlacklist(Message<JsonObject> msg) {
        if (msg.body() == null) {
            msg.fail(412, "the Blacklist IP is null");
            return;
        }
        JsonObject body = msg.body();
        String sql = MessageFormat.format("REPLACE INTO {0} ({1},{2}) values(?,?)", BLTN, BLIC, BLCC);
        JsonArray params = new JsonArray();
        params.add(body.getString("blacklistName"));
        params.add(body.getJsonArray("blacklistBody").toString());
        process(msg, sql, params);
    }

    /**
     * 初始化简写后的常量数据
     */
    public void initShorthand() {
        this.APPTN = DataStoreConstant.APPLICATION_TABLE_NAME;
        this.APPIC = DataStoreConstant.APPLICATION_ID_COLUMN;
        this.APPCC = DataStoreConstant.APPLICATION_CONTENT_COLUMN;
        this.APPIN = DataStoreConstant.APPLICATION_ID_NAME;
        this.APPCN = DataStoreConstant.APPLICATION_CONTENT_NAME;
        this.APITN = DataStoreConstant.API_TABLE_NAME;
        this.APIIC = DataStoreConstant.API_ID_COLUMN;
        this.APICC = DataStoreConstant.API_CONTENT_COLUMN;
        this.APIIN = DataStoreConstant.API_ID_NAME;
        this.APICN = DataStoreConstant.API_CONTENT_NAME;
        this.API_APPIC = DataStoreConstant.API_APP_ID_COLUMN;
        this.API_APPIN = DataStoreConstant.API_APP_ID_NAME;
        this.BLTN = DataStoreConstant.BLACKLIST_TABLE_NAME;
        this.BLIC = DataStoreConstant.BLACKLIST_ID_COLUMN;
        this.BLCC = DataStoreConstant.BLACKLIST_CONTENT_COLUMN;
        this.BLIN = DataStoreConstant.BLACKLIST_ID_NAME;
        this.BLCN = DataStoreConstant.BLACKLIST_CONTENT_NAME;

    }

}
